﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Threading;
using Shared = Faust.Andrew.MTG.Player.Shared;
using Faust.Andrew.MTG;
using Faust.Andrew.MTG.Player.Shared;
namespace Faust.Andrew.MTG.Player
{
    /// <summary>
    /// The Client Class sits between the UI and the server.
    /// </summary>
    public class Client
    {
        //reference to the calling threads dispatcher. This will allow us to raise events on the calling
        //thread.
        private Dispatcher _callingDispatcher = Dispatcher.CurrentDispatcher;
        //Frequency of Milliseconds that the client will Poll the server for new work actions.
        private int _frequency;
        private bool _stopRunning;

        private Shared.IMTGServer _serverProxy;
        protected string _playerName;
        protected int _playerId;

        //Web and database objects
        private Gatherer _gatherer;
        private DataStore _dataStore;        

        public int PlayerID
        {
            get
            {
                return _playerId;
            }
        }

        public event CommunicationsExceptionHandler CommunicationsExceptionEvent;
        protected void FireCommunicationsException(CommunicationExceptionEventArgs e)
        {
            if (CommunicationsExceptionEvent != null)
            {
                if (_callingDispatcher.Thread == System.Threading.Thread.CurrentThread)
                {
                    CommunicationsExceptionEvent(this, e);    
                }
                else
                {
                    _callingDispatcher.BeginInvoke(new Action<CommunicationExceptionEventArgs>(FireCommunicationsException), e);
                }                
            }
        }

        public event NonCriticalExceptionHandler NonCriticalExceptionEvent;
        protected void FireNonCriticalException(NonCriticalExceptionEventArgs e)
        {
            if (NonCriticalExceptionEvent != null)
            {
                if (_callingDispatcher.Thread == System.Threading.Thread.CurrentThread)
                {
                    NonCriticalExceptionEvent(this, e);
                }
                else
                {
                    _callingDispatcher.BeginInvoke(new Action<NonCriticalExceptionEventArgs>(FireNonCriticalException), e);
                }
            }
        }

        ////Events to Raise to the client
        //public event ActionEventHandler<Shared.MessageAction> MessageEvent;
        //protected void FireMessageEvent(ActionEventArgs<Shared.MessageAction> e)
        //{
        //    if (MessageEvent != null)
        //    {
        //        MessageEvent(this, e);
        //    }
        //}

        public event ActionEventHandler<Shared.ShowCardsAction> ShowCardsEvent;
        protected void FireShowCardsEvent(ActionEventArgs<Shared.ShowCardsAction> e)
        {
            if (ShowCardsEvent != null)
            {
                ShowCardsEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.PlanesWalkAction> PlanesWalkEvent;
        protected void FirePlanesWalkEvent(ActionEventArgs<Shared.PlanesWalkAction> e)
        {
            if (PlanesWalkEvent != null)
            {
                PlanesWalkEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.GameStartAction> GameStartEvent;
        protected void FireGameStartEvent(ActionEventArgs<Shared.GameStartAction> e)
        {
            if (GameStartEvent != null)
            {
                GameStartEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.NotificationAction> NotificationEvent;
        protected void FireNotificationEvent(ActionEventArgs<Shared.NotificationAction> e)
        {
            if (NotificationEvent != null)
            {
                NotificationEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.RefreshGameStateAction> RefreshGameStateEvent;
        protected void FireRefreshGameStateEvent(ActionEventArgs<Shared.RefreshGameStateAction> e)
        {
            if (RefreshGameStateEvent != null)
            {
                RefreshGameStateEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.UpdatePlayerAction> UpdatePlayerEvent;
        protected void FireUpdatePlayerEvent(ActionEventArgs<Shared.UpdatePlayerAction> e)
        {
            if (UpdatePlayerEvent != null)
            {
                UpdatePlayerEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.UpdateCardAction> UpdateCardEvent;
        protected void FireUpdateCardEvent(ActionEventArgs<Shared.UpdateCardAction> e)
        {
            if (UpdateCardEvent != null)
            {
                UpdateCardEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.RemoveTokenAction> RemoveTokenEvent;
        protected void FireRemoveTokenEvent(ActionEventArgs<Shared.RemoveTokenAction> e)
        {
            if (RemoveTokenEvent != null)
            {
                RemoveTokenEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.MarkCardAction> MarkCardEvent;
        protected void FireMarkCardEvent(ActionEventArgs<Shared.MarkCardAction> e)
        {
            if (MarkCardEvent != null)
            {
                MarkCardEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.MoveCardAction> MoveCardEvent;
        protected void FireMoveCardEvent(ActionEventArgs<Shared.MoveCardAction> e)
        {
            if (MoveCardEvent != null)
            {
                MoveCardEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.TransferControlAction> TransferControlEvent;
        protected void FireTransferControlEvent(ActionEventArgs<Shared.TransferControlAction> e)
        {
            if (TransferControlEvent != null)
            {
                TransferControlEvent(this, e);
            }
        }

        public event ActionEventHandler<Shared.EndTurnAction> EndTurnEvent;
        protected void FireEndTurnEvent(ActionEventArgs<Shared.EndTurnAction> e)
        {
            if (EndTurnEvent != null)
            {
                EndTurnEvent(this, e);
            }
        }

        protected void FireEventFromThread<T>(T e, Action<T> eventToFire) where T : EventArgs
        {
            if (_callingDispatcher.Thread == System.Threading.Thread.CurrentThread)
            {
                eventToFire(e);
            }
            else
            {
                _callingDispatcher.BeginInvoke(new Action<T, Action<T>>(FireEventFromThread<T>), e, eventToFire);
            }
        }

        /// <param name="name">The targetPlayerID's Name</param>
        public Client(string host, int port, Gatherer gatherer, DataStore dataStore)
        {
            _dataStore = dataStore;

            _gatherer = gatherer;

            _serverProxy = new MTGServerProxy(host, port);
        }

        public void StartClient(int frequency)
        {
            _stopRunning = false;


            if (frequency < 500)
                _frequency = 500;
            else
                _frequency = frequency;
            

            ThreadPool.QueueUserWorkItem(Run);
        }

        public void StopClient()
        {
            _stopRunning = true;
        }

        protected void Run(object state)
        {
            while (!(_stopRunning))
            {
                Thread.Sleep(_frequency);

                Queue<Shared.Action> actions = new Queue<Faust.Andrew.MTG.Player.Shared.Action>();

                try
                {
                    actions = _serverProxy.Poll(_playerId);                    
                }
                catch (Exception e)
                {
                    _stopRunning = true;
                    CommunicationExceptionEventArgs args = new CommunicationExceptionEventArgs(e);
                    FireEventFromThread<CommunicationExceptionEventArgs>(args, FireCommunicationsException);
                    return;
                }

                ProcessActions(actions);
            }
        }

        protected void ProcessActions(Queue<Shared.Action> actions)
        {
            while (actions.Count > 0)
            //foreach (Shared.Action action in actions)
            {
                Shared.Action action = actions.Dequeue();
                
                if (action is ShowCardsAction)
                {
                    ProcessShowCardAction((ShowCardsAction)action);
                }
                else if (action is GameStartAction)
                {
                    ProcessGameStartAction((GameStartAction)action);
                }
                else if (action is NotificationAction)
                {
                    ProcessNotificationAction((NotificationAction)action);
                }
                else if (action is RefreshGameStateAction)
                {
                    ProcessRefreshGameStateAction((RefreshGameStateAction)action);
                }
                else if (action is UpdatePlayerAction)
                {
                    ProcessUpdatePlayerAction((UpdatePlayerAction)action);
                }
                else if (action is UpdateCardAction)
                {
                    ProcessUpdateCardAction((UpdateCardAction)action);
                }
                else if (action is MarkCardAction)
                {
                    ProcessMarkCardAction((MarkCardAction)action);
                }
                else if (action is MoveCardAction)
                {
                    ProcessMoveCardAction((MoveCardAction)action);
                }
                else if (action is TransferControlAction)
                {
                    ProcessTransferControlAction((TransferControlAction)action);
                }
                else if (action is RemoveTokenAction)
                {
                    ProcessRemoveTokenAction((RemoveTokenAction)action);
                }
                else if (action is EndTurnAction)
                {
                    ProcessEndTurnAction((EndTurnAction)action);
                }
                else if (action is PlanesWalkAction)
                {
                    ProcessPlanesWalkAction((PlanesWalkAction)action);
                }
            }
        }

        private void ProcessEndTurnAction(EndTurnAction action)
        {
            ActionEventArgs<EndTurnAction> e = new ActionEventArgs<EndTurnAction>(action);
            FireEventFromThread<ActionEventArgs<EndTurnAction>>(e, FireEndTurnEvent);
        }

        private void ProcessPlanesWalkAction(PlanesWalkAction action)
        {
            ActionEventArgs<PlanesWalkAction> e = new ActionEventArgs<PlanesWalkAction>(action);
            FireEventFromThread<ActionEventArgs<PlanesWalkAction>>(e, FirePlanesWalkEvent);
        }

        private void ProcessTransferControlAction(TransferControlAction action)
        {
            ActionEventArgs<TransferControlAction> e = new ActionEventArgs<TransferControlAction>(action);
            FireEventFromThread<ActionEventArgs<TransferControlAction>>(e, FireTransferControlEvent);
        }
        
        private void ProcessMoveCardAction(MoveCardAction action)
        {
            ActionEventArgs<MoveCardAction> e = new ActionEventArgs<MoveCardAction>(action);
            FireEventFromThread<ActionEventArgs<MoveCardAction>>(e, FireMoveCardEvent);
        }
        
        private void ProcessMarkCardAction(MarkCardAction action)
        {
            ActionEventArgs<MarkCardAction> e = new ActionEventArgs<MarkCardAction>(action);
            FireEventFromThread<ActionEventArgs<MarkCardAction>>(e, FireMarkCardEvent);
        }

        private void ProcessRemoveTokenAction(RemoveTokenAction action)
        {
            ActionEventArgs<RemoveTokenAction> e = new ActionEventArgs<RemoveTokenAction>(action);
            FireEventFromThread<ActionEventArgs<RemoveTokenAction>>(e, FireRemoveTokenEvent);
        }

        private void ProcessUpdateCardAction(UpdateCardAction action)
        {
            if (!action.Card.IsToken)
            {
                _gatherer.LoadCardDetails(action.Card);
            }

            ActionEventArgs<UpdateCardAction> e = new ActionEventArgs<UpdateCardAction>(action);
            FireEventFromThread<ActionEventArgs<UpdateCardAction>>(e, FireUpdateCardEvent);
        }
        
        private void ProcessUpdatePlayerAction(UpdatePlayerAction action)
        {
            ActionEventArgs<UpdatePlayerAction> e = new ActionEventArgs<UpdatePlayerAction>(action);
            FireEventFromThread<ActionEventArgs<UpdatePlayerAction>>(e, FireUpdatePlayerEvent);
        }
        
        private void ProcessRefreshGameStateAction(RefreshGameStateAction action)
        {
            foreach (Shared.GameCard card in action.GameState.Cards)
            {
                if (!card.IsToken)
                {
                    _gatherer.LoadCardDetails(card);
                }
            }

            ActionEventArgs<RefreshGameStateAction> e = new ActionEventArgs<RefreshGameStateAction>(action);
            FireEventFromThread<ActionEventArgs<RefreshGameStateAction>>(e, FireRefreshGameStateEvent);
        }
        
        private void ProcessNotificationAction(NotificationAction action)
        {
            ActionEventArgs<NotificationAction> e = new ActionEventArgs<NotificationAction>(action);
            FireEventFromThread<ActionEventArgs<NotificationAction>>(e, FireNotificationEvent);
        }
        
        private void ProcessGameStartAction(GameStartAction action)
        {
            ActionEventArgs<GameStartAction> e = new ActionEventArgs<GameStartAction>(action);
            FireEventFromThread<ActionEventArgs<GameStartAction>>(e, FireGameStartEvent);
        }
        
        private void ProcessShowCardAction(ShowCardsAction action)
        {
            ActionEventArgs<ShowCardsAction> e = new ActionEventArgs<ShowCardsAction>(action);
            FireEventFromThread<ActionEventArgs<ShowCardsAction>>(e, FireShowCardsEvent);
        }

        //private void ProcessMessageAction(MessageAction action)
        //{
        //    ActionEventArgs<MessageAction> e = new ActionEventArgs<MessageAction>(action);
        //    FireEventFromThread<ActionEventArgs<MessageAction>>(e, FireMessageEvent);
        //}

        /// <summary>
        /// Joins the game and registers your deck.
        /// </summary>
        /// <param name="deck">The deck the player will be playing with</param>
        public bool JoinGame(string playerName, Deck deck)
        {
            try
            {
                _playerId = _serverProxy.JoinGame(playerName, deck);

                return (_playerId > 0);
            }
            catch (Exception e)
            {
                FireNonCriticalException(new NonCriticalExceptionEventArgs(e));
                return false;
            }
        }

        public bool RejoinGame(string playerName)
        {
            try
            {

                _playerId = _serverProxy.RejoinGame(playerName);

                return (_playerId > 0);
            }
            catch (Exception e)
            {
                FireNonCriticalException(new NonCriticalExceptionEventArgs(e));
                return false;
            }
        }

        /*
        public void StartGame()
        {
            _serverProxy.StartGame();
        }

        public void EndGame()
        {
            _serverProxy.EndGame();
        }
        */

        public void SendMessage(List<int> targetPlayerIds, string message)
        {
            _serverProxy.SendMessage(_playerId, targetPlayerIds, message);
        }

        public void ShowCards(List<int> targetPlayerIds, List<int> cardIds)
        {
            _serverProxy.ShowCards(_playerId, targetPlayerIds, cardIds);
        }

        public void Notify(string message)
        {
            _serverProxy.Notify(_playerId, message);
        }

        public void RefreshGameState()
        {
            _serverProxy.RefreshGameState(_playerId);
        }

        public bool UpdateCard(Faust.Andrew.MTG.Player.Shared.GameCard card)
        {
            return _serverProxy.UpdateCard(_playerId, card);
        }

        public bool MarkCard(int cardId, bool isMarked)
        {
            return _serverProxy.MarkCard(_playerId, cardId, isMarked);
        }

        public bool MoveCard(int cardId, Shared.Location location)
        {
            return _serverProxy.MoveCard(_playerId, cardId, location);
        }

        public bool TransferCard(int targetPlayerId, int cardId)
        {
            return _serverProxy.TransferCard(_playerId, targetPlayerId, cardId);
        }

        public bool UpdatePlayer(Shared.Player player)
        {
            return _serverProxy.UpdatePlayer(_playerId, player);
        }

        public void PlayTokenCreature(int playerId, string tokenText, string imageKey, int quantity)
        {
            _serverProxy.PlayTokenCreature(playerId, tokenText, imageKey, quantity);
        }

        public void RemoveTokenCreature(int playerId, int cardId)
        {
            _serverProxy.RemoveTokenCreature(playerId, cardId);
        }

        public int RollDie(int playerId, int sides)
        {
            return _serverProxy.RollDie(playerId, sides);
        }

        public bool ShuffleDeck(int playerId)
        {
            return _serverProxy.ShuffleDeck(playerId);
        }

        public bool SendCardToDeck(int playerId, int cardId, Shared.DeckPosition deckPosition, int offset)
        {
            return _serverProxy.SendCardToDeck(playerId, cardId, deckPosition, offset);
        }

        public Shared.GameCard DrawCard()
        {
            Shared.GameCard card = _serverProxy.DrawCard(_playerId);            

            if (card != null && !card.IsToken)
            {
                _gatherer.LoadCardDetails(card);
            }

            return card;
        }

        public List<Shared.GameCard> GetCardList(int playerId, int targetPlayerId, Shared.Zone zone)
        {
            List<Shared.GameCard> cards = _serverProxy.GetCardList(playerId, targetPlayerId, zone);

            foreach (Shared.GameCard card in cards)
            {
                if (card != null && !card.IsToken)
                {
                    _gatherer.LoadCardDetails(card);
                }
            }

            return cards;
        }

        public void EndTurn(int playerId)
        {
            _serverProxy.EndTurn(playerId);
        }

        public void AttemptPlaneswalk()
        {
            _serverProxy.AttemptPlaneswalk(PlayerID);
        }

        public void RandomReveal(int count, Shared.Zone sourceZone, List<int> targetPlayers)
        {
            _serverProxy.RandomReveal(PlayerID, count, sourceZone, targetPlayers);
        }

        public void RandomDiscard(int count, Shared.Zone sourceZone)
        {
            _serverProxy.RandomDiscard(PlayerID, count, sourceZone);
        }
    }
}
